/* document.cc
* this file is part of libxml++
*
* copyright (C) 2003 by the libxml++ development team
*
* this file is covered by the GNU Lesser General Public License,
* which should be included with libxml++ as the file COPYING.
*/

#include "PreCompile.h"
#include "document.h"
#include "dtd.h"
#include "attribute.h"
#include "nodes/element.h"
#include "nodes/entityreference.h"
#include "nodes/textnode.h"
#include "nodes/commentnode.h"
#include "nodes/cdatanode.h"
#include "nodes/processinginstructionnode.h"
#include "exceptions/internal_error.h"
#include "keepblanks.h"
#include "io/ostreamoutputbuffer.h"
#include "ParserSettings.h"
#include "parsers/domparser.h"

namespace Xml
{
  //Called by libxml whenever it constructs something,
  //such as a node or attribute.
  //This allows us to create a C++ instance for every C instance.
  static void on_libxml_construct(xmlNode* node)
  {
    switch (node->type)
    {
    case XML_ELEMENT_NODE:
      {
        node->_private = new Element(node);
        break;
      }
    case XML_ATTRIBUTE_NODE:
      {
        node->_private = new Attribute(node);
        break;
      }
    case XML_TEXT_NODE:
      {
        node->_private = new TextNode(node);
        break;
      }
    case XML_COMMENT_NODE:
      {
        node->_private = new CommentNode(node);
        break;
      }
    case XML_CDATA_SECTION_NODE:
      {
        node->_private = new CdataNode(node);
        break;
      }
    case XML_PI_NODE:
      {
        node->_private = new ProcessingInstructionNode(node);
        break;
      }
    case XML_DTD_NODE:
      {
        node->_private = new Dtd(reinterpret_cast<xmlDtd*>(node));
        break;
      }
      //case XML_ENTITY_NODE:
      //{
      //  assert(0 && "Warning: XML_ENTITY_NODE not implemented");
      //  //node->_private = new Xml::ProcessingInstructionNode(node);
      //  break;
      //}
    case XML_ENTITY_REF_NODE:
      {
        node->_private = new EntityReference(node);
        break;
      }
    case XML_DOCUMENT_NODE:
      {
        // do nothing ! in case of documents it's the wrapper that is the owner
        break;
      }
    default:
      {
        // good default for release versions
        node->_private = new Node(node);
        assert(0 && "Warning: new node of unknown type created");
        break;
      }
    }
  }

  //Called by libxml whenever it destroys something
  //such as a node or attribute.
  //This allows us to delete the C++ instance for the C instance, if any.
  void on_libxml_destruct(xmlNode* node)
  {
    bool bPrivateDeleted = false;
    if (node->type == ::XML_DTD_NODE)
    {
      Dtd* cppDtd = static_cast<Dtd*>(node->_private);
      if(cppDtd)
      {
        delete cppDtd;
        bPrivateDeleted = true;
      }
    }
    else if (node->type == ::XML_DOCUMENT_NODE)
      // do nothing. See on_libxml_construct for an explanation
      ;
    else
    {
      Node* cppNode =  static_cast<Node*>(node->_private);
      if(cppNode)
      {
        delete cppNode;
        bPrivateDeleted = true;
      }
    }

    //This probably isn't necessary:
    if(bPrivateDeleted)
      node->_private = 0;
  }

  Document::Init::Init()
  {
    xmlInitParser(); //Not always necessary, but necessary for thread safety.
    xmlRegisterNodeDefault(on_libxml_construct);
    xmlDeregisterNodeDefault(on_libxml_destruct);
    xmlThrDefRegisterNodeDefault(on_libxml_construct);
    xmlThrDefDeregisterNodeDefault(on_libxml_destruct);
  }

  Document::Init::~Init()
  {
    xmlCleanupParser(); //As per xmlInitParser(), or memory leak will happen.
  }

  Document::Init Document::init_;

  Document::Document(const Glib::ustring &filename)
  {
    this->file_name = filename;

    ParserSettings settings;

    DomParser parser(filename,settings);
    impl_ = parser.steal_document();
    impl_->_private = this;
  }

  Document::Document(const Glib::ustring &filename,const ParserSettings &settings)
  {
    this->file_name = filename;

    DomParser parser(filename,settings);
    impl_ = parser.steal_document();
    impl_->_private = this;
  }

  Document::Document(xmlDoc* doc)
    : impl_(doc)
  {
    impl_->_private = this;
  }

  Document::~Document()
  {
    xmlFreeDoc(impl_);
  }

  Glib::ustring Document::get_encoding() const
  {
    Glib::ustring encoding;
    if(impl_->encoding)
      encoding = (const char*)impl_->encoding;

    return encoding;
  }

  Dtd* Document::get_internal_subset() const
  {
    xmlDtd* dtd = xmlGetIntSubset(impl_);
    if(!dtd)
      return 0;

    if(!dtd->_private)
      dtd->_private = new Dtd(dtd);

    return reinterpret_cast<Dtd*>(dtd->_private);
  }

  void Document::set_internal_subset(const Glib::ustring& name,
    const Glib::ustring& external_id,
    const Glib::ustring& system_id)
  {
    xmlDtd* dtd = xmlCreateIntSubset(impl_,
      (const xmlChar*)name.c_str(),
      external_id.empty() ? (const xmlChar*)NULL : (const xmlChar*)external_id.c_str(),
      system_id.empty() ? (const xmlChar*)NULL : (const xmlChar*)system_id.c_str());

    if (dtd && !dtd->_private)
      dtd->_private = new Dtd(dtd);
  }

  Element* Document::get_root_node() const
  {
    xmlNode* root = xmlDocGetRootElement(impl_);
    if(root == 0)
      return 0;
    else
      return reinterpret_cast<Element*>(root->_private);
  }

  Element* Document::create_root_node(const Glib::ustring& name,
    const Glib::ustring& ns_uri,
    const Glib::ustring& ns_prefix)
  {
    xmlNode* node = xmlNewDocNode(impl_, 0, (const xmlChar*)name.c_str(), 0);
    xmlDocSetRootElement(impl_, node);

    Element* element = get_root_node();

    if( !ns_uri.empty() )
    {
      element->set_namespace_declaration(ns_uri, ns_prefix);
      element->set_namespace(ns_prefix);
    }

    return element;
  }

  Element* Document::create_root_node_by_import(const Node* node,
    bool recursive)
  {
    //Create the node, by copying:
    xmlNode* imported_node = xmlDocCopyNode(const_cast<xmlNode*>(node->cobj()), impl_, recursive);
    if (!imported_node)
    {
      throw Exception("Unable to import node");
    }

    xmlDocSetRootElement(impl_, imported_node);

    return get_root_node();
  }

  CommentNode* Document::add_comment(const Glib::ustring& content)
  {
    xmlNode* node = xmlNewComment((const xmlChar*)content.c_str());
    if(!node)
    {
      throw InternalError("Cannot create comment node");
    }

    // Use the result, because node can be freed when merging text nodes:
    node = xmlAddChild( (xmlNode*)impl_, node);
    return static_cast<CommentNode*>(node->_private);
  }

  void Document::write_to_file(const Glib::ustring& filename, const Glib::ustring& encoding)
  {
    do_write_to_file(filename, encoding, false);
  }

  void Document::write_to_file_formatted(const Glib::ustring& filename, const Glib::ustring& encoding)
  {
    do_write_to_file(filename, encoding, true);
  }

  Glib::ustring Document::write_to_string(const Glib::ustring& encoding)
  {
    return do_write_to_string(encoding, false);
  }

  Glib::ustring Document::write_to_string_formatted(const Glib::ustring& encoding)
  {
    return do_write_to_string(encoding, true);
  }

  void Document::write_to_stream(std::ostream& output, const Glib::ustring& encoding)
  {
    do_write_to_stream(output, encoding.empty()?get_encoding():encoding, false);
  }

  void Document::write_to_stream_formatted(std::ostream& output, const Glib::ustring& encoding)
  {
    do_write_to_stream(output, encoding.empty()?get_encoding():encoding, true);
  }

  void Document::do_write_to_file(
    const Glib::ustring& filename,
    const Glib::ustring& encoding,
    bool format)
  {
    KeepBlanks k(KeepBlanks::Default);
    xmlIndentTreeOutput = format?1:0;
    int result = 0;

    result = xmlSaveFormatFileEnc(filename.c_str(), impl_, encoding.empty()?NULL:encoding.c_str(), format?1:0);

    if(result == -1)
    {
      throw Exception("do_write_to_file() failed.");
    }
  }

  Glib::ustring Document::do_write_to_string(
    const Glib::ustring& encoding,
    bool format)
  {
    KeepBlanks k(KeepBlanks::Default);
    xmlIndentTreeOutput = format?1:0;
    xmlChar* buffer = 0;
    int length = 0;

    xmlDocDumpFormatMemoryEnc(impl_, &buffer, &length, encoding.empty()?NULL:encoding.c_str(), format?1:0);

    if(!buffer)
    {
      throw Exception("do_write_to_string() failed.");
    }

    // Create a Glib::ustring copy of the buffer

    // Here we force the use of Glib::ustring::ustring( InputIterator begin, InputIterator end )
    // instead of Glib::ustring::ustring( const char*, size_type ) because it
    // expects the length of the string in characters, not in bytes.
    Glib::ustring result( reinterpret_cast<const char *>(buffer), reinterpret_cast<const char *>(buffer + length) );

    // Deletes the original buffer
    xmlFree(buffer);
    // Return a copy of the string
    return result;
  }

  void Document::do_write_to_stream(std::ostream& output, const Glib::ustring& encoding, bool format)
  {
    // TODO assert document encoding is UTF-8 if encoding is different than UTF-8
    OStreamOutputBuffer buffer(output, encoding);
    xmlSaveFormatFileTo(buffer.cobj(), impl_, encoding.c_str(), format?1:0);
  }

  void Document::set_entity_declaration(const Glib::ustring& name, XmlEntityType type,
    const Glib::ustring& publicId, const Glib::ustring& systemId,
    const Glib::ustring& content)
  {
    xmlAddDocEntity( impl_, (const xmlChar*) name.c_str(), type,
      publicId.empty() ? (const xmlChar*)0 : (const xmlChar*)publicId.c_str(),
      systemId.empty() ? (const xmlChar*)0 : (const xmlChar*)systemId.c_str(),
      (const xmlChar*) content.c_str() );
  }

  _xmlEntity* Document::get_entity(const Glib::ustring& name)
  {
    return xmlGetDocEntity(impl_, (const xmlChar*) name.c_str());
  }

  _xmlDoc* Document::cobj()
  {
    return impl_;
  }

  const _xmlDoc* Document::cobj() const
  {
    return impl_;
  }

} //namespace Xml
